﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  [DebuggerDisplay( "Length = {Length}" )]
  internal class ListDataItemIndexLeaf : ListDataItemIndexNode
  {
    #region Constructor

    internal ListDataItemIndexLeaf(
      ListDataItemIndexConfiguration configuration,
      IEqualityComparer<StatFunction> statFunctionComparer,
      int level, 
      int indexStart,
      int length,
      Func<int, int, RawDataItem, IComparer<RawDataItem>, int> binarySearch )
      : base( configuration, statFunctionComparer, level, indexStart, length )
    {
      if( configuration == null )
        throw new ArgumentNullException( "configuration" );

      if( level < 0 || level >= configuration.SortOrder.Count )
        throw new ArgumentOutOfRangeException( "level" );

      if( binarySearch == null )
        throw new ArgumentNullException( "binarySearch" );

      m_memberName = configuration.SortOrder[ level ].PropertyName;
      m_binarySearch = binarySearch;
    }

    #endregion

    internal override void Add( LinkedListNode<object> groupValue )
    {
      if( groupValue == null )
        throw new ArgumentNullException( "groupValue" );

      this.Grow();
    }

    internal override bool Remove( int index )
    {
      // We must remove the current empty node.
      if( this.Length <= 1 )
        return true;

      this.Shrink();
      return false;
    }

    internal override int GetDataItemIndex(
      LinkedListNode<object> groupValue, 
      bool reverseOrder,
      bool skipStartingItem )
    {
      int index;
      this.FindDataItemIndex( groupValue, reverseOrder, skipStartingItem, out index );

      return index;
    }

    internal override bool GetGroupCount( LinkedListNode<int> groupCount )
    {
      if( groupCount != null )
      {
        groupCount.Value += this.Length;
      }

      return true;
    }

    internal override bool GetGroupCount( 
      LinkedListNode<int> groupCount, 
      LinkedListNode<object> groupValue )
    {
      // All items on this level must be counted.
      if( groupValue == null )
        return this.GetGroupCount( groupCount );

      // The item wasn't found, so it is not counted.
      int index;
      if( !this.FindDataItemIndex( groupValue, false, false, out index ) )
        return false;

      // The item was found, we must count it.
      if( groupCount != null )
      {
        groupCount.Value += 1;
      }

      return true;
    }

    internal override bool GetGroupCount( 
      LinkedListNode<int> groupCount, 
      LinkedListNode<object> groupValue, 
      bool reverseOrder, 
      bool skipStartingItem )
    {
      // All items on this level must be counted.
      if( ( groupCount == null ) || ( groupValue == null ) )
        return this.GetGroupCount( groupCount );

      int index;
      this.FindDataItemIndex( groupValue, reverseOrder, skipStartingItem, out index );

      // The item we were looking for isn't part of the group.
      if( ( index < this.IndexStart ) || ( index >= this.IndexStart + this.Length ) )
        return false;

      groupCount.Value += ( reverseOrder )
                            ? index - this.IndexStart + 1
                            : this.IndexStart + this.Length - index;

      return true;
    }

    internal override bool GetGroupCount(
      LinkedListNode<int> groupCount,
      LinkedListNode<object> filterGroupValue,
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem )
    {
      if( filterGroupValue == null )
        return this.GetGroupCount( groupCount, groupValue, reverseOrder, skipStartingItem );

      if( groupCount == null )
        return this.GetGroupCount( groupCount );

      int index;
      bool found = this.FindDataItemIndex( filterGroupValue, reverseOrder, skipStartingItem, out index );

      // The group doesn't exists.
      if( !found )
        return false;

      int startIndex;
      this.FindDataItemIndex( groupValue, reverseOrder, skipStartingItem, out startIndex );

      // The starting item has no impact on the group count.
      if( ( ( !reverseOrder ) && ( startIndex > index ) )
        || ( ( reverseOrder ) && ( startIndex < index ) ) )
        return false;

      groupCount.Value += 1;

      return true;
    }

    private bool FindDataItemIndex(
      LinkedListNode<object> groupValue,
      bool reverseOrder,
      bool skipStartingItem,
      out int index )
    {
      if( groupValue == null )
      {
        if( skipStartingItem )
        {
          index = ( reverseOrder )
                    ? this.IndexStart - 1
                    : this.IndexStart + this.Length;
        }
        else
        {
          index = ( reverseOrder )
                    ? this.IndexStart + this.Length - 1
                    : this.IndexStart;
        }

        return true;
      }

      TargetValue targetValue = new TargetValue( groupValue.Value );
      MemberValueComparer comparer = new MemberValueComparer( m_memberName, this.ItemComparer );
      index = m_binarySearch.Invoke( this.IndexStart, this.Length, targetValue, comparer );

      // The item was found.
      if( index >= 0 )
      {
        if( skipStartingItem )
        {
          index += ( reverseOrder ) ? -1 : 1;
        }

        return true;
      }
      else
      {
        // Find out the position of the item if it would have exist.
        index = ~index;

        // In reverse order, we must return the previous index since it is the
        // position of the closest element to the one we've look for.
        if( reverseOrder )
        {
          index--;
        }

        return false;
      }
    }

    #region Private Fields

    private string m_memberName;
    private Func<int, int, RawDataItem, IComparer<RawDataItem>, int> m_binarySearch;

    #endregion

    #region Private TargetValue Nested Type

    private class TargetValue : RawDataItem
    {
      internal TargetValue( object value )
        : base( value )
      {
      }

      protected override bool FindDataMemberValue( string memberName, out object value )
      {
        value = this.Data;
        return true;
      }
    }

    #endregion

    #region Private MemberValueComparer Nested Type

    private class MemberValueComparer : IComparer<RawDataItem>
    {
      internal MemberValueComparer( string memberName, IComparer comparer )
      {
        if( comparer == null )
          throw new ArgumentNullException( "comparer" );

        m_memberName = memberName;
        m_comparer = comparer;
      }

      public int Compare( RawDataItem x, RawDataItem y )
      {
        if( ( x == null ) || ( y == null ) )
          throw new DataInternalException();

        object firstValue = x.GetDataMemberValue( m_memberName );
        object secondValue = y.GetDataMemberValue( m_memberName );

        return m_comparer.Compare( firstValue, secondValue );
      }

      private string m_memberName;
      private IComparer m_comparer;
    }

    #endregion
  }
}
